/******************************************************************************
	Cobalt - utility libraries for building XNA games
	Copyright (C) 2008 Philip Djonov

	This program is free software; you can redistribute it and/or modify it
	under the terms of the GNU General Public License as published by the Free
	Software Foundation; either version 2 of the License, or (at your option)
	any later version.

	This program is distributed in the hope that it will be useful, but
	WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
	or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
	for more details.

	You should have received a copy of the GNU General Public License along
	with this program; if not, write to the
	
		Free Software Foundation, Inc.
		51 Franklin Street, Fifth Floor
		Boston, MA  02110-1301, USA.
******************************************************************************/

using System;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;

namespace Cobalt
{
	public class Camera
	{
		public Camera()
		{
		}

		private int screenWidth = 1;
		private int screenHeight = 1;
		public void Initialize( int screenWidth, int screenHeight )
		{
			projectionGood = false;
			this.screenWidth = Math.Max( screenWidth, 1 );
			this.screenHeight = Math.Max( screenHeight, 1 );
		}

		#region View Matrix
		private bool viewGood;
		private Matrix view;
		private Matrix orientation;
		public Matrix View
		{
			get
			{
				if( !viewGood )
				{
					UpdateOrientation();
					UpdateView();
					viewGood = true;
				}

				return view;
			}
		}

		public Matrix Oreintation
		{
			get
			{
				if( !viewGood )
				{
					UpdateOrientation();
					UpdateView();
					viewGood = true;
				}

				return orientation;
			}
		}

		private void UpdateOrientation()
		{
			orientation =
				Matrix.CreateRotationX( MathHelper.ToRadians( pitch ) ) *
				Matrix.CreateRotationY( MathHelper.ToRadians( yaw ) );
		}

		private void UpdateView()
		{
			view = Matrix.CreateLookAt( pos, pos + orientation.Forward, Vector3.Up );
		}

		private Vector3 pos;
		public Vector3 Position
		{
			get { return pos; }
			set
			{
				if( pos != value )
				{
					pos = value;
					viewGood = false;
				}
			}
		}

		private float yaw;
		public float Yaw
		{
			get { return yaw; }
			set
			{
				while( value > 360.0F )
					value -= 360.0F;

				while( value < 0 )
					value += 360.0F;

				if( yaw != value )
				{
					yaw = value;
					viewGood = false;
				}
			}
		}

		private float pitch;
		public float Pitch
		{
			get { return pitch; }
			set
			{
				value = MathHelper.Clamp( value, -89, 89 );

				if( value != pitch )
				{
					pitch = value;
					viewGood = false;
				}
			}
		}

		private float movementSpeed = 5;
		public float MovementSpeed
		{
			get { return movementSpeed; }
			set
			{
				if( value < 0 )
					value = 0;

				movementSpeed = value;
			}
		}

		private float rotationSpeed = 100;
		public float RotationSpeed
		{
			get { return rotationSpeed; }
			set
			{
				if( value < 0 )
					value = 0;

				rotationSpeed = value;
			}
		}
		#endregion

		#region Projection Matrix
		private BoundingSphere apertureBounds;

		public BoundingSphere ApertureBounds
		{
			get
			{
				if( !projectionGood )
				{
					UpdateProjection();
					projectionGood = true;
				}

				return apertureBounds;
			}
		}

		private bool projectionGood;
		private Matrix projection;
		public Matrix Projection
		{
			get
			{
				if( !projectionGood )
				{
					UpdateProjection();
					projectionGood = true;
				}

				return projection;
			}
		}

		private void UpdateProjection()
		{
			float aspect = (float)screenWidth / (float)screenHeight;
			projection = Matrix.CreatePerspectiveFieldOfView(
				MathHelper.ToRadians( fov ), aspect, zNear, zFar );

			float R = zNear / (float)Math.Tan( MathHelper.ToRadians( fov ) * 0.5F );
			if( aspect > 1 )
				R *= aspect;

			apertureBounds.Radius = R;
		}

		private float zNear = 0.1F;
		public float ZNear
		{
			get { return zNear; }
			set
			{
				if( value < 0.001F )
					value = 0.001F;

				if( value != zNear )
				{
					zNear = value;
					projectionGood = false;
				}
			}
		}

		private float zFar = 100000.0F;
		public float ZFar
		{
			get { return zFar; }
			set
			{
				if( value != zFar )
				{
					value = zFar;
					projectionGood = false;
				}
			}
		}

		private float fov = 45.0F;
		public float Fov
		{
			get { return fov; }
			set
			{
				value = MathHelper.Clamp( value, 1, 180 );

				if( value != fov )
				{
					fov = value;
					projectionGood = false;
				}
			}
		}
		#endregion
	}
}